package simpleGeom;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;


import processing.core.PApplet;
import processing.core.PGraphics;

public class BasicMesh {
public ArrayList<HasPos3D>nodes=new ArrayList<HasPos3D>();
public ArrayList<AbstractFace> faces=new ArrayList<AbstractFace>();

public BasicMesh(){
	
}

// builds a mesh from faces and generates a list of nodes form the faces.
public BasicMesh( List<? extends AbstractFace> _faces){
	addFacesAndNodes(_faces);
	
}

public Bounds3D getBounds3D(){
	Bounds3D b3d=new Bounds3D();
	b3d.addAll(nodes);
	return b3d;
}

public void scale(float scaleX,float scaleY,float scaleZ){
	for (int i = 0; i < nodes.size(); i++) {
		HasPos3D n = nodes.get(i);
		n.setX(n.getX()*scaleX);
		n.setY(n.getY()*scaleY);
		n.setZ(n.getZ()*scaleZ);
	}
}

public void scale(float factor){
	scale(factor,factor,factor);
}
public void nomalize(float x){
}
public void translate(float x,float y,float z){
	for (int i = 0; i < nodes.size(); i++) {
		HasPos3D n = nodes.get(i);
		n.setX(n.getX()+x);
		n.setY(n.getY()+y);
		n.setZ(n.getZ()+z);
	}
}
public void moveTo(float x,float y,float z){
	Bounds3D b3d=getBounds3D();
	float dX=x-b3d.getCenterX();
	float dY=y-b3d.getCenterY();
	float dZ=z-b3d.getCenterZ();
	translate(dX,dY,dZ);
}
public void center(float x,float y,float z){
	Bounds3D b3d=getBounds3D();
	translate(-b3d.getCenterX(),-b3d.getCenterY(),-b3d.getCenterZ());
}
public void scaleToBounds(float goalX,float goalY,float goalZ){
	Bounds3D b3d=getBounds3D();
	float scaleX=(goalX)/b3d.getDimX();
	float scaleY=(goalY)/b3d.getDimY();
	float scaleZ=(goalZ)/b3d.getDimZ();
}
public void removeOuterFaces(Box3D bounds){
	ArrayList<AbstractFace> newFaces=new ArrayList<AbstractFace>();
	for (int i=0;i<faces.size();i++){
		AbstractFace face=faces.get(i);
		for (int j=0;j<face.getNodesNum();j++){
			HasPos3D p=face.getNode(j);
			if (bounds.contains(p)){
				System.out.println("inside");
				newFaces.add(face);
				j=face.getNodesNum();
			}
		}
	}
	faces=newFaces;
	removeUnusedNodes();
}
public void load(Vector<SimpleTriangle>triangles){
	faces=new ArrayList<AbstractFace>();	
	nodes=new ArrayList<HasPos3D>();
	for (int i=0;i<triangles.size();i++){
		SimpleTriangle tri=triangles.get(i);
		faces.add(tri);
		for (int j=0;j<tri.getNodesNum();j++){
			HasPos3D p=tri.getNode(j);
			p.setId(-1);
		}
	}
	for (int i=0;i<faces.size();i++){
		AbstractFace tri=faces.get(i);
		for (int j=0;j<tri.getNodesNum();j++){
			HasPos3D p=tri.getNode(j);
			if (p.getId()<0){
				nodes.add(p);
				p.setId(nodes.size());
			}
		}
	}
}
public void loadVertices(String file) {
	nodes=new ArrayList<HasPos3D>();
	DataInputStream in = null;
	BufferedReader br = null;
	try {
		FileInputStream fstream = new FileInputStream(file);
		in = new DataInputStream(fstream);
		br = new BufferedReader(new InputStreamReader(in));
		String strLine;
		try {
			strLine = br.readLine();
			while (strLine != null) {
				int comment=0;
				if (strLine.startsWith("v")) {
					Node node=new Node();
					nodes.add(node);
					String[] data = strLine.split(" ");
					node.x= Float.valueOf(data[1]);
					node.y= Float.valueOf(data[2]);
					node.z= Float.valueOf(data[3]);
					if (data.length>5){
						if ("#".equals(data[4])){
							 comment=Integer.valueOf(data[5]);
							node.comment=(short)comment;
						}
					}
				}
				strLine = br.readLine();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	} catch (Exception e) {// Catch exception if any
		System.err.println("Error: " + e.getMessage());
	}
}
public void loadFaces(String file) {
	faces=new ArrayList<AbstractFace>();
	DataInputStream in = null;
	BufferedReader br = null;
	try {
		FileInputStream fstream = new FileInputStream(file);
		in = new DataInputStream(fstream);
		br = new BufferedReader(new InputStreamReader(in));
		String strLine;
		try {
			strLine = br.readLine();
			while (strLine != null) {
				if (strLine.startsWith("f")) {
					ArrayList<HasPos3D> face=new ArrayList<HasPos3D>();
					int comment=0;
					if (strLine.contains("#")){
						String[] segments=strLine.split("#");
						strLine=segments[0];
						String partComment=segments[1];
						String[] cComments = partComment.split(" ");
						if (cComments.length>1){
							comment=Integer.valueOf(cComments[1]);
						}
					}
					
					String[] data = strLine.split(" ");
					for (int i = 1; i < data.length; i++) {
						String[]number=data[i].split("/");
						int nodeId=Integer.valueOf(number[0]);
						face.add(nodes.get(nodeId-1));
					}
					
					SimpleTriangle f = new SimpleTriangle(face.get(0),
							face.get(1),face.get(2));
					faces.add(f);
					if (face.size()==4){
						f = new SimpleTriangle(face.get(2),
								face.get(3),face.get(0));
						faces.add(f);
					}
				}
				strLine = br.readLine();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	} catch (Exception e) {// Catch exception if any
		System.err.println("Error: " + e.getMessage());
	}
}

public void load(String file){
	nodes=new ArrayList<HasPos3D>();
	faces=new ArrayList<AbstractFace>();
	loadVertices(file);
	System.out.println("vertices loaded: "+nodes.size());
	loadFaces(file);
	System.out.println("faces loaded: "+faces.size());
	removeUnusedNodes();
	
}

	public void saveObj(String file) {
		System.out.println("start save: "+file);
		
		FileWriter fw;
		try {
			fw = new FileWriter(file + ".obj");
			BufferedWriter bw = new BufferedWriter(fw);
		int idV = 1;
		for (int i = 0; i < faces.size(); i++) {
			AbstractFace f = faces.get(i);
			for (int j = 0; j < f.getNodesNum(); j++) {
				Vec3D n = (Vec3D) f.getNode(j);
				n.id=0;
			}
		}
		for (int i = 0; i < faces.size(); i++) {
			AbstractFace f = faces.get(i);
			for (int j = 0; j < f.getNodesNum(); j++) {
				Vec3D n = (Vec3D) f.getNode(j);
				if (n.getId() == 0) {
					n.setId(idV);
					idV++;
					bw.write("v " + n.x + " " + n.y + " " + n.z);
					bw.newLine();
				}
			}
		}

		for (int i = 0; i < faces.size(); i++) {
			AbstractFace f = faces.get(i);
			String string = "f";
			for (int j = 0; j < f.getNodesNum(); j++) {
				Vec3D n = (Vec3D) f.getNode(j);
				string += " " + n.getId();
			}
			bw.write(string);
			bw.newLine();
		}

		bw.flush(); // Writes the remaining data to the file
		bw.close();
		System.out.println("obj saved: "+file);
		}
		catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
	}

	
public void display3D(PGraphics g){
	for (int i=0;i<faces.size();i++){
		AbstractFace face=faces.get(i);
		face.display3D(g);
	}
}
public void addFacesAndNodes(List<? extends AbstractFace>_faces){
	for (int i=0;i<_faces.size();i++){
		AbstractFace t=_faces.get(i);
		for (int j=0;j<t.getNodesNum();j++){
			HasPos3D n=t.getNode(j);
			n.setId(-1);
		}
	}
	for (int i=0;i<nodes.size();i++){
		HasPos3D p=nodes.get(i);
		p.setId(i);
	}
	for (int i=0;i<_faces.size();i++){
		AbstractFace t=_faces.get(i);
		for (int j=0;j<t.getNodesNum();j++){
			HasPos3D n=t.getNode(j);
			if (n.getId()==-1){
				n.setId(nodes.size());
				nodes.add(n);
			}
		}
	}
	faces.addAll(_faces);
}

public void removeUnusedNodes(){
	for (int i=0;i<nodes.size();i++){
		HasPos3D p=nodes.get(i);
		p.setId(-1);
	}
	ArrayList<HasPos3D> newNodes=new ArrayList<HasPos3D>();
	for (int i=0;i<faces.size();i++){
		AbstractFace t=faces.get(i);
		for (int j=0;j<t.getNodesNum();j++){
			HasPos3D n=t.getNode(j);
			if (n.getId()==-1){
				n.setId(newNodes.size());
				newNodes.add(n);
			}
		}
	}
	nodes=newNodes;
}
public ArrayList<AbstractFace> remove(Box3D b3D){
	System.out.println("before Faces: "+faces.size());
	ArrayList<AbstractFace>newFaces=new ArrayList<AbstractFace>();
	ArrayList<AbstractFace>enclosedFaces=new ArrayList<AbstractFace>();
	for (int i=0;i<faces.size();i++){
		AbstractFace face=faces.get(i);
		boolean isInside=true;
		for (int j=0;j<face.getNodesNum();j++){
			HasPos3D node=face.getNode(j);
			if (!b3D.contains(node)){
				isInside=false;
				break;
			}
		}
		if (!isInside){
			newFaces.add(face);
		}
		else{
			enclosedFaces.add(face);
		}
	}
	faces=newFaces;
	System.out.println("after Faces: "+faces.size());
	removeUnusedNodes();
	return enclosedFaces;
}
public ArrayList<HasPos3D> getNodes(Box3D b3D){
	ArrayList<HasPos3D> boxNodes=new ArrayList<HasPos3D>();
	for (int i=0;i<nodes.size();i++){
		HasPos3D node=nodes.get(i);
		if (b3D.contains(node.getX(), node.getY(), node.getZ())){
			boxNodes.add(node);
		}
	}
	return boxNodes;
}

public void analyseFaces(){
	float minArea=100000;
	for (int i=0;i<faces.size();i++){
		AbstractFace st= faces.get(i);
		float cArea=st.getArea();
		if (cArea<minArea){
			minArea=cArea;
		}
	}
	System.out.println("minArea: "+minArea);
}
public static boolean contains(float x,float y,float z,float rX,float rY,float rZ,List<? extends AbstractFace>faces){
	int nI=0;
	Vec3D start=new Vec3D(x,y,z);
	Vec3D end=new Vec3D(x+rX,y+rY,z+rZ);
	for (int i=0;i<faces.size();i++){
		SimpleTriangle face=(SimpleTriangle)faces.get(i);
		if (Face3D.rayTriIntersection(face.getNode(0), face.getNode(1), face.getNode(2), start, end)!=null){
			nI++;
		}
	}
	
	return nI%2==1;
}
public  boolean contains(float x,float y,float z){
	int nI=0;
	Vec3D start=new Vec3D(x,y,z);
	Vec3D end=new Vec3D(x,y,z+1);
	for (int i=0;i<faces.size();i++){
		SimpleTriangle face=(SimpleTriangle) faces.get(i);
		Triangle3D tri3D=new Triangle3D(face.getNode(2), face.getNode(1), face.getNode(0));
		
		//if (tri3D.intersect_RayTriangle(start,end)!=null){
			if (tri3D.intersect_VerRayTriangle(start.x,start.y,start.z)!=null){
		//if (Triangle3D.rayTriIntersection(face.getNode(0), face.getNode(1), face.getNode(2), start, end)!=null){
			nI++;
		}
	}
	return nI%2==1;
}
}